package org.femto.math.vis;

import java.util.*;
import org.femto.math.linalg.*;
import org.femto.math.Util;

public abstract class Manipulable {

    public class NamedManipulator<M extends Manipulator> {
        private M manipulator;
        private int modifier;
        private boolean active;

        public NamedManipulator(M manipulator, int modifier, boolean active) {
            this.manipulator = manipulator;
            this.modifier = modifier;
            this.active = active;
        }

        public M getManipulator() {
            return manipulator;
        }

        public int getModifier() {
            return modifier;
        }

        public boolean isActive() {
            return active;
        }

        public void setActive(boolean active) {
            this.active = active;
        }
    }

    private Map<String, NamedManipulator> manips = new HashMap<String, NamedManipulator>();

    private int width, height;
    private double aspect;

    protected void addManipulator(String name, Manipulator manipulator, int modifier) {
        NamedManipulator namedManip = new NamedManipulator(manipulator, modifier, false);
        manips.put(name, namedManip);
    }

    public Set<Map.Entry<String,NamedManipulator>> getManipulatorEntrySet() {
        return manips.entrySet();
    }

    private NamedManipulator getNamedManipulator(String name) throws IllegalArgumentException {
        for(Map.Entry entry : manips.entrySet()) {
            if(((String)entry.getKey()).equals(name)) {
                return (NamedManipulator)entry.getValue();
            }
        }
        throw new IllegalArgumentException("No such manipulator added: `" + name + "'");
    }

    public Manipulator getManipulator(String name) throws IllegalArgumentException {
        return getNamedManipulator(name).manipulator;
    }

    private NamedManipulator getNamedManipulator(int modifier) {
        for(Map.Entry entry : manips.entrySet()) {
            NamedManipulator manip = (NamedManipulator)entry.getValue();
            if(manip.modifier == modifier && manip.active == true) {
                return manip;
            }
        }
        return null;
    }

    private String getManipulatorName(NamedManipulator manipulator) {
        for(Map.Entry entry : manips.entrySet()) {
            if(((NamedManipulator) entry.getValue()) == manipulator) {
                return (String) entry.getKey();
            }
        }
        return null;
    }

    public void setManipulatorState(String name, boolean active) {
        getNamedManipulator(name).active = active;
    }

    public void setActiveManipulator(String name) {
        for(Map.Entry entry : manips.entrySet()) {
            ((NamedManipulator)entry.getValue()).setActive(((String)entry.getKey()).equals(name));
        }
        onActivateManipulator(name);
    }

    protected void reshapeManipulators(int width, int height) {
        this.width = width;
        this.height = height;
        aspect = (double)width/(double)height;
    }

    private Vec3 normalizeScreenPos(int x, int y) {
        return new Vec3(Util.remap(x, 0, (double)width, -aspect, aspect), Util.remap(y, 0, (double)height, -1, 1), 0);
    }

    public void beginDrag(int x, int y, int modifier) {
        NamedManipulator manip = getNamedManipulator(modifier);

        if(manip != null) {
            manip.manipulator.beginDrag(normalizeScreenPos(x,y));
            onBeginDragging(getManipulatorName(manip));
        }
    }

    public void drag(int x, int y, int modifier) {
        NamedManipulator manip = getNamedManipulator(modifier);

        if(manip != null) {
            manip.manipulator.drag(normalizeScreenPos(x,y));
            onDragging(getManipulatorName(manip));
        }
    }

    public void endDrag(int x, int y, int modifier) {
        NamedManipulator manip = getNamedManipulator(modifier);
        
        if(manip != null) {
            manip.manipulator.endDrag(normalizeScreenPos(x,y));
            onEndDragging(getManipulatorName(manip));
        }
    }
    
    public void move(int x, int y, int modifier) {
        for(Map.Entry entry : manips.entrySet()) {
            NamedManipulator manip = (NamedManipulator)entry.getValue();
            if(manip.active == true) {
                manip.manipulator.move(normalizeScreenPos(x,y));
            }
        }
    }
    
    public void renderManipulators(javax.media.opengl.GL gl) {
        for(Map.Entry entry : manips.entrySet()) {
            NamedManipulator manip = (NamedManipulator)entry.getValue();
            if(manip.active == true) {
                manip.manipulator.render(gl);
            }
        }
    }

    protected abstract void onActivateManipulator(String name);
    protected abstract void onBeginDragging(String name);
    protected abstract void onDragging(String name);
    protected abstract void onEndDragging(String name);

}
